/*
# Copyright (c) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/
#include <native_buffer/native_buffer.h>
#include "hilog/log.h"
#include "ohos_codec_data.h"

namespace webrtc {
namespace ohos {

CodecData::~CodecData() {
    CodecData::ShutDown();
    formatInfo = nullptr;
    inputFrameCount_ = 0;
    outputFrameCount_ = 0;
}

void CodecData::Start() {
    isRunning_.store(true);
    inputCond_.notify_all();
    outputCond_.notify_all();
}

void CodecData::ShutDown() {
    isRunning_.store(false);
    inputCond_.notify_all();
    outputCond_.notify_all();
}

OH_NativeBuffer_Config *CreateNativeBufferConfig(OH_AVBuffer *buffer) {
    OH_NativeBuffer *ohNativeBuffer = OH_AVBuffer_GetNativeBuffer(buffer);
    OH_NativeBuffer_Config *config = new OH_NativeBuffer_Config;
    if (ohNativeBuffer != nullptr) {
        OH_NativeBuffer_GetConfig(ohNativeBuffer, config);
        int32_t ret = OH_NativeBuffer_Unreference(ohNativeBuffer);
        if (ret != AV_ERR_OK) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "NativeBuffer Unreference Default");
        }
        ohNativeBuffer = nullptr;
    }
    return config;
}

int32_t CodecData::InputData(CodecBufferInfo &info, std::chrono::milliseconds time) {
    if (info.GetBuff() == nullptr || this->formatInfo == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "info.buff_ or formatInfo nullptr");
        return AV_ERR_UNKNOWN;
    }
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "InputData enter");
    //{
        std::unique_lock<std::mutex> lock(this->inputMutex_);
        bool condRet = this->inputCond_.wait_for(lock, time, [this]() {
            return !this->inputBufferInfoQueue_.empty() || !isRunning_;
        });
        if (!isRunning_) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "CoderData !isRunning");
            return AV_ERR_IO;
        }
        if (this->inputBufferInfoQueue_.empty()) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData",
                        "inputBufferInfoQueue_ is empty, overtime, cond ret: %{public}d", condRet);
            return AV_ERR_TIMEOUT;
        }
        OH_AVCodecBufferAttr attr = {0, 0, 0, 0};
        CodecBufferInfo bufferInfo;
        bufferInfo = this->inputBufferInfoQueue_.front();
        this->inputBufferInfoQueue_.pop();
        this->inputFrameCount_++;
    //}
    OH_NativeBuffer_Config *config = CreateNativeBufferConfig(bufferInfo.GetAVBuff());
    if(config->stride != formatInfo->videoWidth) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "stride != videoWidth");
        delete config;
        return AV_ERR_IO;
    }
    delete config;
    info.GetAttr(&attr);
    memcpy(bufferInfo.GetBuff(), info.GetBuff(), attr.size);
    bufferInfo.SetAttr(&attr);
    info.SetBufferIndex(bufferInfo.GetBufferIndex());
    if (attr.flags & AVCODEC_BUFFER_FLAGS_EOS) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "Catch EOS");
        return AV_ERR_UNKNOWN;
    }
    return AV_ERR_OK;
}

int32_t CodecData::OutputData(CodecBufferInfo &info) {
    std::unique_lock<std::mutex> lock(this->outputMutex_);
    this->outputCond_.wait(
        lock, [this]() { return !this->outputBufferInfoQueue_.empty() || !isRunning_; });
    if(!isRunning_) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "CoderData", "CoderData !isRunning");
        return AV_ERR_IO;
    }
    info = this->outputBufferInfoQueue_.front();
    this->outputBufferInfoQueue_.pop();
    this->outputFrameCount_++;
    lock.unlock();
    return AV_ERR_OK;
}

void CodecData::DataCallback::OnCodecError(OH_AVCodec *videoEnc, int32_t errorCode, void *userData) {
    (void)videoEnc;
    (void)errorCode;
    (void)userData;
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "CodecCallback", "AVCodec errorCode %{public}d", errorCode);
}

void CodecData::DataCallback::OnCodecFormatChange(OH_AVCodec *videoEnc, OH_AVFormat *format, void *userData) {
    (void)videoEnc;
    (void)format;
    (void)userData;
    if (userData == nullptr) {
        return;
    }
    (void)userData;
    CodecData *codecUserData = static_cast<CodecData *>(userData);
    std::unique_lock<std::mutex> lock(codecUserData->outputMutex_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "CodecCallback", "AVCodec FormatChange");
}

void CodecData::DataCallback::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    if (userData == nullptr) {
        return;
    }
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OnNeedInputBuffer enter");
    (void)codec;
    CodecData *codecUserData = static_cast<CodecData *>(userData);
    std::unique_lock<std::mutex> lock(codecUserData->inputMutex_);
    codecUserData->inputBufferInfoQueue_.emplace(index, buffer);
    codecUserData->inputCond_.notify_all();
}

void CodecData::DataCallback::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    if (userData == nullptr) {
        return;
    }
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OnNewOutputBuffer enter");
    (void)codec;
    CodecData *codecUserData = static_cast<CodecData *>(userData);
    std::unique_lock<std::mutex> lock(codecUserData->outputMutex_);
    codecUserData->outputBufferInfoQueue_.emplace(index, buffer);
    codecUserData->outputCond_.notify_all();
}

}
}